﻿import io
import struct
import string
import wbox

def dump(s):
    return " ".join(map(lambda c: "{0:02X}".format(ord(c)), s))

class melpath:
    def __init__(self, root):
        self.root = root

    def parameter(self):
        return "%s\\Resource\\param.wpa" % (self.root)

    def program(self, name):
        return "%s\\Resource\\POU\\Body\\%s.wpg" % (self.root, name)

    def comment(self, name):
        return "%s\\Resource\\Others\\%s.wcd" % (self.root, name)

    def _comment(self, name):
        # 이전 버전에서는 POU 아래에 있었다.
        return "%s\\Resource\\POU\\Others\\%s.wcd" % (self.root, name)    
        
class BinaryReader(file):
    def __init__(self, filename):
        file.__init__(self, filename, "rb")

    def byte(self):
        return struct.unpack("B", self.read(1))[0]

    def word(self):
        return struct.unpack("H", self.read(2))[0]

    def dword(self):
        return struct.unpack("L", self.read(4))[0]

    def long(self):
        return struct.unpack("l", self.read(4))[0]


class BinaryWriter(file):
    def __init__(self, filename):
        file.__init__(self, filename, "wb")

    def byte(self, n):
        self.write(struct.pack("B", n))

    def word(self, n):
        self.write(struct.pack("H", n))

    def dword(self, n):
        self.write(struct.pack("L", n))

    def long(self, n):
        self.write(struct.pack("l", n))
        
        
deviceInfo = {
    0x82: (';' , 10, 0),
    0x90: ('M' , 10, 1),
    0x91: ('SM', 10, 1),
    0x92: ('L' , 10, 1),
    0x93: ('F' , 10, 1),
    0x94: ('V' , 10, 1),
    0x98: ('S' , 10, 1),
    0x99: ('TR', 10, 1),
    0x9C: ('X' , 16, 1),
    0x9D: ('Y' , 16, 1),
    0x9E: ('FX', 16, 1),
    0x9F: ('FY', 16, 1),
    0xA0: ('B' , 16, 1),
    0xA1: ('SB', 16, 1),
    0xA2: ('DX', 16, 1),
    0xA3: ('DY', 16, 1),
    0xA8: ('D' , 10, 0),
    0xA9: ('SD', 10, 0),
    0xAA: ('FD', 10, 0),
    0xAB: ('G' , 10, 0),
    0xAF: ('R' , 10, 0),
    0xB0: ('ZR', 10, 0),
    0xB4: ('W' , 16, 0),
    0xB5: ('SW', 16, 0),
    0xC0: ('TC', 10, 1),
    0xC1: ('TS', 10, 1),
    0xC2: ('T' , 10, 0),
    0xC3: ('CC', 10, 1),
    0xC4: ('CS', 10, 1),
    0xC5: ('C' , 10, 0),
    0xC6: ('SC', 10, 1),
    0xC7: ('SS', 10, 1),
    0xC8: ('ST', 10, 0),
    0xCC: ('Z' , 10, 0),
    0xCD: ('V' , 10, 0),
    0xD0: ('P' , 10, 0),
    0xD2: ('N' , 10, 0),
    0xD8: ('U' , 16, 0),
    0xD9: ('J' , 10, 0),
    0xDC: ('BL', 10, 1),
    0xE8: ('K' , 10, 0),
    0xE9: ('K' , 10, 0),
    0xEA: ('H' , 16, 0),
    0xEB: ('H' , 16, 0),
    0xEC: ('E' , 10, 0),
    0xEE: ('"' , 10, 0),
    0xF0: ('Z' , 10, 0),
    0xF1: ('K' , 10, 0),
    0xF2: ('.' , 16, 1),
    0xF3: ('@' , 10, 0),
    0xF4: ('V' , 10, 0),
    0xF6: ('ZZ', 10, 0),
    0xF8: ('U' , 16, 0),
    0xF9: ('J' , 10, 0),
    0xFC: ('BL', 10, 0),
    }
constant_devices = (0xE8, 0xE9, 0xEA, 0xEB, "K", "H")
timer_devices = (0xC2, 0xC5, 0xC8, "T", "C", "ST")

    
class DeviceInfo(object):
    def __init__(self, nameTable):
        self.nameTable = nameTable
        self.codeTable = {}
        # 확장코드는 주가 되는 디바이스의 앞/뒤에 붙혀 디바이스의 의미를 확장시켜주는 코드로 0xF0 이상의 값을 가진다.
        self.extCodeTable = {}
        def _name(key):
            name, base, bit = nameTable[key]
            if key < 0xF0:
                # 먼저 기록된 것을 유지한다.
                # K와 H을 위한 것으로 앞선 코드(워드용)가 유지되도록 한다.
                if name not in self.codeTable:
                    self.codeTable[name] = (key, base, bit)
            else:
                self.extCodeTable[name] = (key, base, bit)
        map(_name, sorted(nameTable.keys()))
        
    def getName(self, code):
        return self.nameTable[code]
        
    def getCode(self, name, isext=False):
        return self.codeTable[name] if not isext else self.extCodeTable[name]
        
    def getExtCode(self, name):
        return self.extCodeTable[name]
        
# TODO:
#     기존에 deviceInfo에 접근하는 코드들을 이 객체를 사용하도록 변경할 것.
devinfo  = DeviceInfo(deviceInfo)

# 기존 코드와의 호환을 위해서
maincode = devinfo.codeTable
extcode  = devinfo.extCodeTable



from opcode import commandTable, uncommandTable
import opcode

def tonum(s):
    n = 0
    d = 1
    for c in s:
        n += ord(c) * d
        d *= 256
    return n

def getcommand(s):
    return "%s(%d)" % opcode.getname(s)

def getname(s):
    code = ord(s[0])
    if code < 0x90: # command
        return getcommand(s)
    else:
        return _getname(s)

def _getname(s):
    code, address = strToDeviceCode(s)
    name, base, bit = deviceInfo[code]
    if code == 0x82 or code == 0xEE:
        return name, address
    return name, {10:"%d", 16:"%X"}[base] % address
    
def getDeviceName(code, address):
    name, base, bit = deviceInfo[code]
    if code == 0x82 or code == 0xEE:
        return name, address
    return name, {10:"%d", 16:"%X"}[base] % address
    
def strToDeviceCode(s):
    code = ord(s[0])
    if code == 0x82: # note
        return code, (ord(s[1]), s[2:])
    address = s[1:]
    if code == 0xEE: # string
        return code, address
    if len(address) == 3:
        address = "\x00" + address
    return code, struct.unpack(numberType[len(address)], address)[0]
    
def isBitDevice(code):
    return deviceInfo[code][2] == 1
    
def isTimerDevice(code):
    return code in timer_devices
    
def isConstant(code):
    return code in constant_devices

def getcode(name):
    pos = 0
    m = buffer_re.match(name)
    if m != None:
        print m.groups()
        pos = m.end()
    m = dnamere.match(name, pos)
    print m.groups()
    
    
def deviceName2Code(name):
    """ 디바이스의 이름을 코드로 바꾼다.
    코멘트 파일에서 디바이스를 개별적으로 다룰 때에 사용하는 것으로
    인덱스 레지스터를 포함하지 않으므로 표현이 단순하다.
    """
    code = 0
    address = 0
    sep = name.find("\\")
    if sep != -1:
        ext, addr = wbox.il.opparser.splitdevicename(name[:sep])
        code, base, bit = devinfo.getExtCode(ext)
        code = code << 8
        address = addr << 16
        name = name[sep + 1:]
    name, addr = wbox.il.opparser.splitdevicename(name)
    code2, base, bit = maincode[name]
    code += code2
    address += addr
    return code, address
    
def deviceCode2Name(code, address):
    name = "".join(getDeviceName(code & 0xff, address & 0xffff))
    if (code & 0xff00) != 0: # 확장코드가 있는가?
        code = (code >> 8) & 0xff
        address = (address >> 16) & 0xffff
        name = "".join(getDeviceName(code, address)) + "\\" + name
    return name
    

class GxRawStream(io.BytesIO):
    def __init__(self, initial_bytes=None):
        super(GxRawStream, self).__init__(initial_bytes)
        
    def read(self, length=1):
        data = super(GxRawStream, self).read(length)
        if not data:
            raise EOFError()
        return data

    def readint(self, format="l"):
        return struct.unpack(format, self.read(struct.calcsize(format)))[0]
        
    def readushort(self):
        return self.readint("H")

    def readDataBlock(self):
        try:
            length = self.readint("H")
            if length != 0xffff:
                return self.read(length - 2)
        except EOFError:
            pass
        return "" 
        
    def __iter__(self):
        return self
        
    def next(self):
        length = self.readint("H")
        if length == 0xffff:
            raise StopIteration()
        return length, self.read(length - 2)


import struct

numberType = {1:"B", 2:"H", 4:"L"}

from error import EndException


class GxStream(object):
    def __init__(self, data):
        self.data = data
        self.index = 0
        self.end = len(data)
        self.queue = [None]

    def get(self):
        if self.index >= self.end:
            return None
        i = self.index
        length = ord(self.data[i])
        if length == 255: # 헤드의 크기를 맞추기 위해 끝부분에 채워져 있다.
            self.index += 1
            return ""
        self.index += length
        return self.data[i:self.index]

    def fill(self, amount):
        if amount < len(self.queue):
            return
        for i in range(amount - len(self.queue) + 1):
            self.queue.append(self.get())

    def consume(self):
        self.queue.pop(0)

    def LA(self, i):
        self.fill(i)
        return self.queue[i]

    def skip(self, n):
        self.index += n

    def isEnd(self):
        return self.data[self.index] == '\xff'

    def peekNext(self):
        return self.LA(1)

    def nextIsDevice(self):
        next = self.LA(1)
        if next == None or len(next) < 3:
            return False
        return next[1] > '\x80'
        
    def __iter__(self):
        while True:
            next = self.getNext()
            if next == "":
                break
            yield next

    def getNext(self):
        self.consume()
        if self.LA(0) == None:
            raise EndException
        return self.LA(0)

    def getNextCode(self):
        code = self.getNext()
        assert code[0] == chr(len(code)) and code[0] == code[-1]
        return code[1:-1]

    def getNextDevice(self):
        code = self.getNext()
        assert ord(code[0]) == len(code) and code[0] == code[-1]
        devName = ord(code[1])
        if devName == 0x82: # note
            return devName, (ord(code[2]), code[3:-1])
        address = code[2:-1]
        if devName == 0xEE: # string
            return devName, address
        if len(address) == 3:
            address = "\x00" + address
        return devName, struct.unpack(numberType[len(address)], address)[0]

    def getDevice(self):
        return GxDevice.fromCode(self)

    def getCommand(self):
        return GxCommand.fromCode(self)
        
    def read(self, length=1):
        start = self.index
        self.index += length
        return self.data[start:self.index]
        
    def readend(self):
        return self.read(self.end - self.index)
        
    def readint(self, format="l"):
        return struct.unpack(format, self.read(struct.calcsize(format)))[0]
        
    def readData(self):
        if self.index < self.end:
            length = self.readint("B")
            if length != 0 and length != 0xff:
                return chr(length) + self.read(length - 1)
        return ""
        
    def readDataBlock(self):
        length = self.readint("H")
        if length != 0xffff:
            return self.read(length - 2)
        return ""
        
        
class GxFileHeader(object):
    def __init__(self):
        self.length = 0
        self.items = {}
        
    def read(self, stream):
        header = stream.readDataBlock()
        print repr(header)
        header = GxRawStream(header)
        while True:
            length = header.readint("B")
            if length == 0xff:
                break
            key = header.readint("B")
            value = header.read(length - 2)
            if key in self.items:
                raise Exception("duplicate key: {0}".format(key))
            self.items[key] = value
            print key, repr(value)
        print self.items
        

class GxObject(object):
    def __repr__(self):
        return "%s: %s" % (self.__class__.__name__, self.__str__())
        

class GxHeader(GxObject):
    def __init__(self, data):
        self.data = data
        self._size = len(data)
        self._headers = {}
        stream = GxStream(data)
        stream.skip(2) # 40 00 - HEAD size
        for s in stream:
            self._headers[ord(s[1])] = s[2:]
            
    def __str__(self):
        return str(self._headers)        
    
    
class NamedList(list):
    _names_ = {}
    
    def __init__(self, init):
        list.__init__(self, init)
        
    def __getattr__(self, name):
        if name in self._names_:
            index = self._names_[name]
            return list.__getitem__(self, index)
        return list.__getitem__(self, name)
        
    def valist(self, name):
        if name in self._names_:
            index = self._names_[name]
            return self[index + 1:]
        return []
        
    def __str__(self):
        if self._names_:
            L = []
            for key, value in self._names_.items():
                L.append("{0} {1}".format(key, self[value]))
            return "\n".join(L)
        else:
            return list.__str__(self)        

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def alignComment(comment):
    """ 8자 단위로 줄바꿈되는 GX Developer에 표시될 때 보기에 편하도록 정렬한다.
    """
    result = []
    last = ""
    for comment in comment.split():
        # 8보다 긴 경우 자투리만 남기고 앞 부분은 그대로 반영한다.
        length = len(comment)
        if  length >= 8:
            end = length - length % 8
            result.append(comment[:end])
            comment = comment[end:]
            
        if last == "":
            if comment != "\\":
                last = comment
        else:
            # "\"은 줄바꿈 역할을 한다.
            if comment == "\\":
                join = last
                last = ""
            else:
                joinLen = len(last + comment)
                if joinLen == 8:
                    join = last + comment
                    last = ""
                elif joinLen < 8:
                    join = last.strip() + " " + comment
                    last = ""
                else:
                    join = last
                    last = comment
            result.append((join + " "*8)[:8])
    if last:
        result.append((last + " "*8)[:8])
    
    # "_"은 채움 역할만을 하도록 모두 " "으로 바꾼다.
    return "".join(result).replace("_", " ")
    
def joinComment(*args):
    def align(comment):
        comment = alignComment(comment)
        reqsp = 8 - len(comment) % 8
        if reqsp < 8:
            comment = comment + " "*reqsp
        return comment
    return "".join(map(align, args))

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if __name__ == '__main__':
    gx = GxCommentFile()
    gx.load('COMMENT.wcd')
    gx.save('result.wcd')




